/*global analogue*/
/*jslint indent: 4*/

/*Analogue.wysiwym 1.11.12, Copyright (c) 2011 Analogue Web Design LLC, all rights reserved.*/
analogue.extend((function (document, window, $) {
    'use strict';

    var build = new Date(1324004457014),

        Chunk = function (instance) {
            return this.init(instance);
        },

        iframe = (function () {
            var setFrame = function (listener) {
                    var div = $('<div/>'),
                        id = 'i' + Math.floor(Math.random() * 99999),
                        iframe;
                    div.set('html', '<iframe style="display:none" src="about:blank" id="' + id + '" name="' + id + '" onload="analogue.wysiwym.iframe.loaded(\'' + id + '\')"></iframe>').appendTo(document.body);
                    iframe = $('#' + id)[0];
                    iframe.oncomplete = listener;
                    return id;
                };
            return {
                init: function (form, listener) {
                    form.setAttribute('target', setFrame(listener));
                },
                loaded: function (id) {
                    var content,
                        iframe = $('#' + id)[0];
                    if (iframe.contentDocument) {
                        content = iframe.contentDocument;
                    } else if (iframe.contentWindow) {
                        content = iframe.contentWindow.document;
                    } else {
                        content = window.frames[id].document;
                    }
                    if (content.location.href === 'about:blank') {
                        return;
                    }
                    if (typeof iframe.oncomplete === 'function') {
                        iframe.oncomplete(content.body.innerHTML);
                    }
                }
            };
        }()),

        extendRegExp = function (regex, pre, post) {
            var flags = '',
                pattern = regex.toString(),
                result = pattern.match(/\/([gim]*)$/);
            pre = pre || '';
            post = post || '';
            if (result === null) {
                flags = result[0];
            }
            pattern = pattern.replace(/(^\/|\/[gim]*$)/g, '');
            pattern = pre + pattern + post;
            return new RegExp(pattern, flags);
        },

        fixEolChars = function (text) {
            text = text.replace(/\r\n/g, '\n');
            text = text.replace(/\r/g, '\n');
            return text;
        },

        isIe = $.detect.msie,

        isOpera = window.opera && Object.prototype.toString.call(window.opera) === '[object Opera]',

        State = function (element) {
            this.init(element);
            if ((!this.text && element.selectionStart) || element.selectionStart === 0) {
                this.text = element.value;
            }
            return this;
        },

        stringOf = function (text, x) {
            var a = [];
            a.length = x + 1;
            return a.join(text);
        },

        versioning = [1, String(build.getFullYear()).slice(2), String((build.getMonth() + 1) / 100).slice(2)],

        wysiwym = function (settings) {
            var cacheRange = function () {
                    this.RangeCache = document.selection.createRange();
                },
                tools = {
                    addDefinition: function (chunk, definition) {
                        /*jslint regexp: true*/
                        var definitions = '',
                            reference = 0,
                            toAdd = {},
                            addDefNumber = function (def) {
                                reference += 1;
                                def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, '  [' + reference + ']:');
                                definitions += '\n' + def;
                            },
                            getLink = function (wholeMatch, link, id, end) {
                                if (toAdd[id]) {
                                    addDefNumber(toAdd[id]);
                                    return link + reference + end;
                                }
                                return wholeMatch;
                            },
                            regex = /(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;
                        chunk.before = tools.stripDefinitions(chunk.before, toAdd);
                        chunk.selection = tools.stripDefinitions(chunk.selection, toAdd);
                        chunk.after = tools.stripDefinitions(chunk.after, toAdd);
                        chunk.before = chunk.before.replace(regex, getLink);
                        if (definition) {
                            addDefNumber(definition);
                        } else {
                            chunk.selection = chunk.selection.replace(regex, getLink);
                        }
                        chunk.after = chunk.after.replace(regex, getLink);
                        if (chunk.after) {
                            chunk.after = chunk.after.replace(/\n*$/, '');
                        }
                        if (!chunk.after) {
                            chunk.selection = chunk.selection.replace(/\n*$/, '');
                        }
                        chunk.after += '\n\n' + definitions;
                        return reference;
                    },
                    autoIndent: function (chunk) {
                        /*jslint regexp: true*/
                        chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+\-]|\d+[.])[ \t]*\n$/, '\n\n');
                        chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, '\n\n');
                        chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, '\n\n');
                        if (/(\n|^)[ ]{0,3}([*+\-])[ \t]+.*\n$/.test(chunk.before)) {
                            if (tools.list) {
                                return tools.list(chunk, 0);
                            }
                        }
                        if (/(\n|^)[ ]{0,3}(\d+[.])[ \t]+.*\n$/.test(chunk.before)) {
                            if (tools.list) {
                                return tools.list(chunk, 1);
                            }
                        }
                        if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) {
                            if (tools.blockquote) {
                                return tools.blockquote(chunk);
                            }
                        }
                    },
                    blockquote: function (chunk) {
                        /*jslint regexp: true, unparam: true*/
                        var defaultText = 'Blockquote',
                            replaceBlanksInTags = function (useBracket) {
                                var replacement = useBracket ? '> ' : '';
                                if (chunk.startTag) {
                                    chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/, function (match, markdown) {
                                        return '\n' + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + '\n';
                                    });
                                }
                                if (chunk.endTag) {
                                    chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/, function (match, markdown) {
                                        return '\n' + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + '\n';
                                    });
                                }
                            };
                        chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/, function (match, linesBefore, text, linesAfter) {
                            chunk.before += linesBefore;
                            chunk.after = linesAfter + chunk.after;
                            return text;
                        });
                        chunk.before = chunk.before.replace(/(>[ \t]*)$/, function (match, blankLine) {
                            chunk.selection = blankLine + chunk.selection;
                            return '';
                        });
                        chunk.selection = chunk.selection.replace(/^(\s|>)+$/, '');
                        chunk.selection = chunk.selection || defaultText;
                        if (chunk.before) {
                            chunk.before = chunk.before.replace(/\n?$/, '\n');
                        }
                        if (chunk.after) {
                            chunk.after = chunk.after.replace(/^\n?/, '\n');
                        }
                        chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/, function (match) {
                            chunk.startTag = match;
                            return '';
                        });
                        chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/, function (match) {
                            chunk.endTag = match;
                            return '';
                        });
                        if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) {
                            tools.wrap(chunk, chunk.maxLength - 2);
                            chunk.selection = chunk.selection.replace(/^/gm, '> ');
                            replaceBlanksInTags(1);
                            chunk.addBlankLines();
                        } else {
                            chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, '');
                            tools.unwrap(chunk);
                            replaceBlanksInTags(0);
                            if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) {
                                chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, '\n\n');
                            }
                            if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) {
                                chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, '\n\n');
                            }
                        }
                        if (!/\n/.test(chunk.selection)) {
                            chunk.selection = chunk.selection.replace(/^(> *)/, function (match, blanks) {
                                chunk.startTag += blanks;
                                return '';
                            });
                        }
                    },
                    bold: function (chunk) {
                        return tools.emphasis(chunk, 2, 'Bold Text');
                    },
                    bulletedList: function (chunk, useDefaultText) {
                        return tools.list(chunk, 0, useDefaultText);
                    },
                    click: function (button, textarea) {
                        return function (event) {
                            tools.execute(textarea, button.command);
                            event.preventDefault();
                        };
                    },
                    emphasis: function (chunk, stars, defaultText) {
                        var markup,
                            prevStars,
                            starsAfter,
                            starsBefore,
                            whitespace;
                        chunk.trimWhitespace();
                        chunk.selection = chunk.selection.replace(/\n{2,}/g, '\n');
                        chunk.before.search(/(\**$)/);
                        starsBefore = RegExp.$1;
                        chunk.after.search(/(^\**)/);
                        starsAfter = RegExp.$1;
                        prevStars = Math.min(starsBefore.length, starsAfter.length);
                        if ((prevStars >= stars) && (prevStars !== 2 || stars !== 1)) {
                            chunk.before = chunk.before.replace(new RegExp('[*]{' + stars + '}$', ''), '');
                            chunk.after = chunk.after.replace(new RegExp('^[*]{' + stars + '}', ''), '');
                        } else if (!chunk.selection && starsAfter) {
                            chunk.after = chunk.after.replace(/^([*_]*)/, '');
                            chunk.before = chunk.before.replace(/(\s?)$/, '');
                            whitespace = RegExp.$1;
                            chunk.before = chunk.before + starsAfter + whitespace;
                        } else {
                            if (!chunk.selection && !starsAfter) {
                                chunk.selection = defaultText;
                            }
                            markup = stars === 1 ? '*' : '**';
                            chunk.before = chunk.before + markup;
                            chunk.after = markup + chunk.after;
                        }
                    },
                    execute: function (element, command, options) {
                        element.focus();
                        if (command) {
                            var callback,
                                chunks,
                                state = new State(element);
                            if (!state) {
                                return;
                            }
                            chunks = state.getChunks();
                            callback = function () {
                                element.focus();
                                if (chunks) {
                                    state.setChunks(chunks);
                                }
                                state.update();
                            };
                            if (!command(chunks, options, callback)) {
                                callback();
                            }
                        }
                    },
                    fixLink: function (link) {
                        if (/^([\w\-]+(?:\.[\w\-]+)*)@((?:[\w\-]+\.)*\w[\w\-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/.test(link)) {
                            link = 'mailto:' + link;
                        } else {
                            link = link.replace('http://http://', 'http://');
                            link = link.replace('http://https://', 'https://');
                            link = link.replace('http://ftp://', 'ftp://');
                            if (!/^((ht|f)tp(s)?:\/\/)/i.test(link)) {
                                if (link.indexOf('www') === 0 || link.indexOf('/') !== 0) {
                                    link = 'http://' + link;
                                }
                            }
                        }
                        return link;
                    },
                    getButtons: function () {
                        var buttons = [
                                {
                                    command: tools.bold,
                                    title: 'Bold (Ctrl+B)',
                                    type: 'Bold'
                                },
                                {
                                    command: tools.italic,
                                    separate: 1,
                                    title: 'Italic (Ctrl+I)',
                                    type: 'Italic'
                                },
                                {
                                    command: tools.link,
                                    title: 'Insert Link',
                                    type: 'Link'
                                },
                                {
                                    command: tools.image,
                                    separate: 1,
                                    title: 'Insert Image',
                                    type: 'Image'
                                },
                                {
                                    command: tools.numberedList,
                                    title: 'Numbered List',
                                    type: 'Numbered'
                                },
                                {
                                    command: tools.bulletedList,
                                    title: 'Bulleted List',
                                    type: 'Bulleted'
                                },
                                {
                                    command: tools.horizontalRule,
                                    separate: isIe ? 1 : 0,
                                    title: 'Horizontal Rule',
                                    type: 'Rule'
                                },
                                {
                                    command: false,
                                    execute: function (event) {
                                        window.alert('Analogue.WysiWym.js\n\nVersion: ' + versioning.join('.') + ' (' + parseInt(build.valueOf() / 1E3, 10) + ')' + '\nCopyright (c) ' + build.getFullYear().toString() + ' Analogue Web Design, LLC\nAll rights reserved.\n\nKeyboard Shortcuts:\nInsert a line break: SHIFT+ENTER\nMake the selected text bold: CTRL+B\nItalicize the selected text: CTRL+I\nCopy the selected text:  CTRL+C\nPaste text: CTRL+V\nUndo the last action: CTRL+Z\nRedo the last action: CTRL+Y');
                                        event.preventDefault();
                                    },
                                    title: 'About wysiwym.js',
                                    type: 'Info'
                                }
                            ];
                        if (isIe) {
                            buttons.splice(7, 0, {
                                command: tools.spellCheck,
                                title : 'Check Spelling',
                                type: 'Spelling'
                            });
                        }
                        tools.getButtons = buttons;
                    },
                    heading: function (chunk, hash) {
                        var markup = stringOf('#', hash) + ' ';
                        chunk.selection = chunk.selection.replace(/\s+/g, ' ');
                        chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, '');
                        if (!chunk.selection) {
                            if (/\S[ ]*$/.test(chunk.before)) {
                                chunk.addBlankLines(1, 0, true);
                            }
                            chunk.startTag = markup;
                            chunk.selection = settings.headings[hash - 1];
                            chunk.endTag = '\n';
                            return;
                        }
                        chunk.findTags(/#+[ ]*/, /[ ]*#+/);
                        chunk.startTag = markup;
                        if (chunk.after.search(/^\n/) === -1) {
                            chunk.endTag = '\n';
                        }
                    },
                    horizontalRule: function (chunk) {
                        chunk.startTag = '----------\n';
                        chunk.selection = '';
                        chunk.addBlankLines(2, 1, true);
                    },
                    image: function (chunk, options, callback) {
                        return tools.reference(chunk, 1, callback, options);
                    },
                    italic: function (chunk) {
                        return tools.emphasis(chunk, 1, 'Italic Text');
                    },
                    lineBreak: function (chunk) {
                        chunk.before = chunk.before.replace(/\n$/, '  \n');
                        chunk.seleciton = '';
                    },
                    link: function (chunk, options, callback) {
                        return tools.reference(chunk, 0, callback, options);
                    },
                    list: function (chunk, isNumberedList) {
                        /*jslint regexp: true*/
                        var bullet = isNumberedList ? 1 : '-',
                            getItemPrefix = function () {
                                var prefix = ' ';
                                if (isNumberedList) {
                                    prefix = ' ' + bullet + '. ';
                                    bullet += 1;
                                } else {
                                    prefix = ' ' + bullet + ' ';
                                }
                                return prefix;
                            },
                            getPrefixedItem = function (itemText) {
                                if (typeof isNumberedList === 'undefined') {
                                    isNumberedList = /^\s*\d/.test(itemText);
                                }
                                itemText = itemText.replace(/^[ ]{0,3}([*+\-]|\d+[.])\s/gm, function () {
                                    return getItemPrefix();
                                });
                                return itemText;
                            },
                            hasDigits,
                            linesAfter = 1,
                            linesBefore = 2,
                            nextItemsRegex = /^\n*(([ ]{0,3}([*+\-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+\-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/,
                            prefix,
                            previousItemsRegex = /(\n|^)(([ ]{0,3}([*+\-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+\-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/,
                            spaces;
                        chunk.findTags(/(\n|^)*[ ]{0,3}([*+\-]|\d+[.])\s+/, null);
                        if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) {
                            chunk.before += chunk.startTag;
                            chunk.startTag = '';
                        }
                        if (chunk.startTag) {
                            hasDigits = /\d+[.]/.test(chunk.startTag);
                            chunk.startTag = '';
                            chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, '\n');
                            tools.unwrap(chunk);
                            chunk.addBlankLines();
                            if (hasDigits) {
                                chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem);
                            }
                            if (isNumberedList === hasDigits) {
                                return;
                            }
                        }
                        chunk.before = chunk.before.replace(previousItemsRegex, function (itemText) {
                            if (/^\s*([*+\-])/.test(itemText)) {
                                bullet = RegExp.$1;
                            }
                            linesBefore = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
                            return getPrefixedItem(itemText);
                        });
                        if (!chunk.selection) {
                            chunk.selection = 'List item';
                        }
                        prefix = getItemPrefix();
                        chunk.after = chunk.after.replace(nextItemsRegex, function (itemText) {
                            linesAfter = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
                            return getPrefixedItem(itemText);
                        });
                        chunk.trimWhitespace(1);
                        chunk.addBlankLines(linesBefore, linesAfter, 1);
                        chunk.startTag = prefix;
                        spaces = prefix.replace(/./g, ' ');
                        tools.wrap(chunk, chunk.maxLength - spaces.length);
                        chunk.selection = chunk.selection.replace(/\n/g, '\n' + spaces);
                    },
                    numberedList: function (chunk) {
                        return tools.list(chunk, 1);
                    },
                    prefixes: '(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)',
                    prompt: function (textarea, isImage, callback) {
                        if ($('.wysiwymPrompt').length > 0) {
                            $('.wysiwymPrompt').first().remove();
                        }
                        var form = $('<form/>'),
                            upload = settings.upload ? $('<form/>') : '';
                        form.set({
                            action: '#',
                            'class': 'wysiwymPrompt'
                        }).setStyle('left', ((textarea.toolbar[0].offsetWidth / 2) - 186) + 'px');
                        if (isImage) {
                            form.addEvent('submit', function (event) {
                                var fields = this.elements,
                                    src = fields.src.value,
                                    link = src,
                                    options = [];
                                if (src === 'http://' || src === null || src.length === 0 || /^\s+$/.test(src)) {
                                    window.alert('You must enter a URL to create an image!');
                                    fields.src.focus();
                                    return event.preventDefault();
                                }
                                link = tools.fixLink(link);
                                if (settings.full) {
                                    if (fields.border.checked) {
                                        options.push('border');
                                    }
                                    if (fields.align.value.length > 0 && !/^\s+$/.test(fields.align.value)) {
                                        options.push(fields.align.value);
                                    }
                                    if (options.length > 0) {
                                        link += ' "' + options.join(' ') + '"';
                                    }
                                }
                                this.parentNode.removeChild(this);
                                event.preventDefault();
                                return callback(link);
                            });
                            $('<fieldset><legend>Insert an Image</legend><label class="wysiwymBlock">URL:</label><input class="' + (settings.upload ? 'wysiwymImage' : 'wysiwymField') + '" name="src"/><br/>' + (settings.full ? '<label class="wysiwymBlock">Align:</label><select name="align"><option value="">Default</option><option value="left">Left</option><option value="right">Right</option></select><label>Border? <input type="checkbox" name="border"/></label><br/>' : '') + '<button class="wysiwymSubmit" type="submit">Submit</button><button class="wysiwymCancel" type="reset">Cancel</button></fieldset>').appendTo(form);
                            if (settings.upload) {
                                $('<label><input name="image" size="1" title="Upload an Image" type="file"/></label>').appendTo(upload);
                                upload.set({
                                    action: settings.upload.action || settings.upload,
                                    enctype: 'multipart/form-data',
                                    'class': 'wysiwymUpload',
                                    method: 'post'
                                }).insertAfter($('input', form[0]).first());
                                $('input', upload).first().addEvent('change', function () {
                                    iframe.init(this.form, function (response) {
                                        /*jslint regexp: true*/
                                        var fields = $('.wysiwymPrompt')[0].elements;
                                        response = response.replace(/<\/?pre*[^>]+>/gi, '');
                                        if (response.indexOf('ERROR') > -1) {
                                            window.alert(response);
                                        } else {
                                            fields.src.value = response;
                                            fields.align.focus();
                                        }
                                    });
                                    this.form.submit();
                                });
                                if (settings.upload.height) {
                                    $('<input name="height" type="hidden" value="' + settings.upload.height + '"/>').appendTo(upload);
                                }
                                if (settings.upload.path) {
                                    $('<input name="path" type="hidden" value="' + settings.upload.path + '"/>').appendTo(upload);
                                }
                                if (settings.upload.width) {
                                    $('<input name="width" type="hidden" value="' + settings.upload.width + '"/>').appendTo(upload);
                                }
                            }
                        } else {
                            form.addEvent('submit', function (event) {
                                var fields = this.elements,
                                    href = fields.href.value,
                                    link = href,
                                    title = settings.full ? fields.title.value : '';
                                if (href === 'http://' || href === null || href.length === 0 || /^\s+$/.test(href)) {
                                    window.alert('You must enter a URL to create a link!');
                                    fields.href.focus();
                                    return event.preventDefault();
                                }
                                link = tools.fixLink(link);
                                if (title.length > 0 && !/^\s+$/.test(title)) {
                                    link += ' "' + title + '"';
                                }
                                this.parentNode.removeChild(this);
                                event.preventDefault();
                                return callback(link);
                            });
                            $('<fieldset><legend>Insert a Link</legend><label class="wysiwymBlock">URL:</label><input class="wysiwymField" name="href"/><br/>' + (settings.full ? '<label class="wysiwymBlock">Title:</label><input class="wysiwymField" name="title"/><br/>' : '') + '<button class="wysiwymSubmit" type="submit">Submit</button><button class="wysiwymCancel" type="reset">Cancel</button></fieldset>').appendTo(form);
                        }
                        $('.wysiwymCancel', form).first().addEvent('click', function (event) {
                            form.remove();
                            event.preventDefault();
                            return callback(null);
                        });
                        form.appendTo(textarea.toolbar);
                        $('input', form)[0].focus();
                    },
                    reference: function (chunk, isImage, callback) {
                        /*jslint regexp: true*/
                        chunk.trimWhitespace();
                        chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
                        if (chunk.endTag.length > 1) {
                            chunk.startTag = chunk.startTag.replace(/!?\[/, '');
                            chunk.endTag = '';
                            tools.addDefinition(chunk, null);
                        } else {
                            if (/\n\n/.test(chunk.selection)) {
                                tools.addDefinition(chunk, null);
                                return;
                            }
                            tools.prompt(chunk.element, isImage, function (link) {
                                if (link !== null) {
                                    var definition = ' [999]: ' + link,
                                        i = tools.addDefinition(chunk, definition);
                                    chunk.startTag = chunk.endTag = '';
                                    chunk.startTag = isImage ? '![' : '[';
                                    chunk.endTag = '][' + i + ']';
                                    if (!chunk.selection) {
                                        if (isImage) {
                                            chunk.selection = 'alt text';
                                        } else {
                                            chunk.selection = 'link text';
                                        }
                                    }
                                }
                                callback();
                            });
                            return true;
                        }
                    },
                    spellCheck: isIe ? function (chunk) {
                        try {
                            return new window.ActiveXObject('ieSpell.ieSpellExtension').CheckDocumentNode(chunk.element);
                        } catch (e) {
                            window.open('http://www.iespell.com/download.php', 'Download');
                        }
                    } : null,
                    stripDefinitions: function (text, definitions) {
                        /*jslint regexp: true, unparam: true*/
                        text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm, function (match, id, link, newlines, title) {
                            definitions[id] = match.replace(/\s*$/, '');
                            if (newlines) {
                                definitions[id] = match.replace(/["(](.+?)[")]$/, '');
                                return newlines + title;
                            }
                            return '';
                        });
                        return text;
                    },
                    unwrap: function (chunk) {
                        chunk.selection = chunk.selection.replace(new RegExp('([^\\n])\\n(?!(\\n|' + tools.prefixes + '))', 'g'), '$1 $2');
                    },
                    wrap: function (chunk, len) {
                        var regex = new RegExp('(.{1,' + len + '})( +|$\\n?)', 'gm');
                        tools.unwrap(chunk);
                        chunk.selection = chunk.selection.replace(regex, function (line, marked) {
                            if (new RegExp('^' + tools.prefixes, '').test(line)) {
                                return line;
                            }
                            return marked + '\n';
                        });
                        chunk.selection = chunk.selection.replace(/\s+$/, '');
                    }
                };
            settings = settings || {};
            settings.headings = settings.headings || ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
            tools.getButtons();
            $('fieldset.wysiwymEditor').forEach(function () {
                var button,
                    buttons = tools.getButtons,
                    fieldset = $(this),
                    i,
                    ii,
                    textarea = $('textarea', this)[0],
                    toolbar = $('<div/>');
                fieldset.textarea = textarea;
                toolbar.set({
                    'class': 'wysiwymToolbar',
                    tabindex: -1
                });
                $('<select><option>Style...</option><option>' + settings.headings.join('</option><option>') + '</option><option>Blockquote</option></select>').appendTo(toolbar);
                $('select', toolbar).first().addEvent('change', function (event) {
                    if (this.selectedIndex === settings.headings.length  + 1) {
                        tools.execute(textarea, tools.blockquote);
                    } else if (this.selectedIndex > 0) {
                        tools.execute(textarea, tools.heading, this.selectedIndex);
                    }
                    this.selectedIndex = 0;
                    event.preventDefault();
                }).appendTo(toolbar);
                for (i = 0, ii = buttons.length; i < ii; i += 1) {
                    button = buttons[i];
                    $('<a/>').set({
                        'class': 'wysiwym' + button.type,
                        href: '#',
                        tabindex: '-1',
                        text: button.title,
                        title: button.title
                    }).addEvent('click', button.command ? tools.click(button, textarea) : button.execute).addEvent('mousedown', isIe ? cacheRange.bind(textarea) : null).appendTo(toolbar);
                    if (button.separate) {
                        $('<span/>').set({
                            'class': 'wysiwymSeparator',
                            text: '|'
                        }).appendTo(toolbar);
                    }
                }
                toolbar.insertBefore(textarea);
                fieldset.toolbar = textarea.toolbar = toolbar;
                $(textarea).addEvent('keyup', function (event) {
                    if (event.which === 13) {
                        textarea.RangeCache = false;
                        if (!event.shiftKey && !event.ctrlKey && !event.metaKey) {
                            tools.execute(textarea, tools.autoIndent);
                        } else {
                            tools.execute(textarea, tools.lineBreak);
                            event.preventDefault();
                        }
                    }
                }).addEvent(isOpera ? 'keypress' : 'keydown', function (event) {
                    if (event.ctrlKey) {
                        switch (String.fromCharCode(event.which).toLowerCase()) {
                        case 'b':
                            tools.execute(textarea, tools.bold);
                            break;
                        case 'i':
                            tools.execute(textarea, tools.italic);
                            break;
                        default:
                            return;
                        }
                        event.preventDefault();
                    }
                });
                if (isIe) {
                    $(textarea).addEvent('keydown', function (event) {
                        if (event.which === 27) {
                            event.preventDefault();
                        }
                    });
                }
            });
            return this;
        };

    Chunk.prototype = {
        addBlankLines: function (linesBefore, linesAfter, findExtraNewlines) {
            var regexText,
                replacementText;
            linesBefore = linesBefore || 1;
            linesAfter = linesAfter || 1;
            linesBefore += 1;
            linesAfter += 1;
            this.selection = this.selection.replace(/(^\n*)/, '');
            this.startTag = this.startTag + RegExp.$1;
            this.selection = this.selection.replace(/(\n*$)/, '');
            this.endTag = this.endTag + RegExp.$1;
            this.startTag = this.startTag.replace(/(^\n*)/, '');
            this.before = this.before + RegExp.$1;
            this.endTag = this.endTag.replace(/(\n*$)/, '');
            this.after = this.after + RegExp.$1;
            if (this.before) {
                regexText = replacementText = '';
                linesBefore -= 1;
                while (linesBefore) {
                    regexText += '\\n?';
                    replacementText += '\n';
                    linesBefore -= 1;
                }
                if (findExtraNewlines) {
                    regexText = '\\n*';
                }
                this.before = this.before.replace(new RegExp(regexText + '$', ''), replacementText);
            }
            if (this.after) {
                regexText = replacementText = '';
                linesAfter -= 1;
                while (linesAfter) {
                    regexText += '\\n?';
                    replacementText += '\n';
                    linesAfter -= 1;
                }
                if (findExtraNewlines) {
                    regexText = '\\n*';
                }
                this.after = this.after.replace(new RegExp(regexText, ''), replacementText);
            }
        },
        findTags: function (startRegex, endRegex) {
            var chunk = this,
                regex;
            if (startRegex) {
                regex = extendRegExp(startRegex, '', '$');
                this.before = this.before.replace(regex, function (match) {
                    chunk.startTag = chunk.startTag + match;
                    return '';
                });
                regex = extendRegExp(startRegex, '^', '');
                this.selection = this.selection.replace(regex, function (match) {
                    chunk.startTag = chunk.startTag + match;
                    return '';
                });
            }
            if (endRegex) {
                regex = extendRegExp(endRegex, '', '$');
                this.selection = this.selection.replace(regex, function (match) {
                    chunk.endTag = match + chunk.endTag;
                    return '';
                });
                regex = extendRegExp(endRegex, '^', '');
                this.after = this.after.replace(regex, function (match) {
                    chunk.endTag = match + chunk.endTag;
                    return '';
                });
            }
        },
        init: function (instance) {
            this.after = fixEolChars(instance.text.substring(instance.end));
            this.before = fixEolChars(instance.text.substring(0, instance.start));
            this.endTag = '';
            this.maxLength = instance.maxLength;
            this.scrollTop = instance.scrollTop;
            this.selection = fixEolChars(instance.text.substring(instance.start, instance.end));
            this.startTag = '';
            this.element = instance.element;
            return this;
        },
        trimWhitespace: function (remove) {
            this.selection = this.selection.replace(/^(\s*)/, '');
            if (!remove) {
                this.before += RegExp.$1;
            }
            this.selection = this.selection.replace(/(\s*)$/, '');
            if (!remove) {
                this.after = RegExp.$1 + this.after;
            }
        }
    };

    State.prototype = {
        getChunks: function () {
            return new Chunk(this);
        },
        init: function (element) {
            this.element = element;
            this.scrollTop = element.scrollTop;
            if (element.selectionStart || element.selectionStart === 0) {
                this.start = element.selectionStart;
                this.end = element.selectionEnd;
            } else if (document.selection) {
                var fixedRange,
                    len,
                    markedRange,
                    marker = '\x07',
                    range,
                    value;
                this.text = fixEolChars(element.value);
                if (element.RangeCache) {
                    range = element.RangeCache;
                    element.RangeCache = false;
                } else {
                    range = document.selection.createRange();
                }
                fixedRange = fixEolChars(range.text);
                markedRange = marker + fixedRange + marker;
                range.text = markedRange;
                value = fixEolChars(element.value);
                range.moveStart('character', -markedRange.length);
                range.text = fixedRange;
                this.start = value.indexOf(marker);
                this.end = value.lastIndexOf(marker) - marker.length;
                len = this.text.length - fixEolChars(element.value).length;
                if (len) {
                    range.moveStart('character', -fixedRange.length);
                    len -= 1;
                    while (len) {
                        fixedRange += '\n';
                        this.end += 1;
                        len -= 1;
                    }
                    range.text = fixedRange;
                }
                this.setRange();
            }
        },
        setChunks: function (chunk) {
            chunk.before = chunk.before + chunk.startTag;
            chunk.after = chunk.endTag + chunk.after;
            if (isOpera) {
                chunk.before = chunk.before.replace(/\n/g, '\r\n');
                chunk.selection = chunk.selection.replace(/\n/g, '\r\n');
                chunk.after = chunk.after.replace(/\n/g, '\r\n');
            }
            this.start = chunk.before.length;
            this.end = chunk.before.length + chunk.selection.length;
            this.text = chunk.before + chunk.selection + chunk.after;
            this.scrollTop = chunk.scrollTop;
        },
        setRange: function () {
            var element = this.element,
                range;
            element.focus();
            if (typeof element.selectionStart !== 'undefined' && !isOpera) {
                element.selectionStart = this.start;
                element.selectionEnd = this.end;
                element.scrollTop = this.scrollTop;
            } else if (document.selection) {
                if (document.activeElement && document.activeElement !== element) {
                    return;
                }
                range = element.createTextRange();
                range.moveStart('character', -element.value.length);
                range.moveEnd('character', -element.value.length);
                range.moveEnd('character', this.end);
                range.moveStart('character', this.start);
                range.select();
            }
        },
        update: function () {
            var element = this.element;
            if (typeof this.text !== 'undefined' && this.text !== element.value) {
                element.value = this.text;
            }
            this.maxLength = parseInt(element.cols, 10);
            this.setRange();
            element.scrollTop = this.scrollTop;
        }
    };

    return {
        init: function () {
            $.wysiwym = function (settings) {
                return wysiwym(settings);
            };
            $.wysiwym.iframe = iframe;
            $.wysiwym.version = versioning.join('.') + ' (' + parseInt(build.valueOf() / 1E3, 10) + ')';
            return this;
        }
    };
}(this.document, this, analogue)));